diff options
author | jcid <devnull@localhost> | 2008-09-24 18:44:40 +0200 |
---|---|---|
committer | jcid <devnull@localhost> | 2008-09-24 18:44:40 +0200 |
commit | c377e06400f138325a9a9d43d91a9272691867a1 (patch) | |
tree | 49f3ca1c46af11a058a68714899d4137ec717618 /dw/textblock.cc | |
parent | 642f9b3e747859a7256ea12fab9f9ed50aa9253a (diff) |
- Moved the dw2 tree into dillo2's tree.
Diffstat (limited to 'dw/textblock.cc')
-rw-r--r-- | dw/textblock.cc | 2087 |
1 files changed, 2087 insertions, 0 deletions
diff --git a/dw/textblock.cc b/dw/textblock.cc new file mode 100644 index 00000000..0ada8027 --- /dev/null +++ b/dw/textblock.cc @@ -0,0 +1,2087 @@ +/* + * Dillo Widget + * + * Copyright 2005-2007 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + + +#include "textblock.hh" +#include "../lout/misc.hh" + +#include <stdio.h> +#include <limits.h> + +namespace dw { + +int Textblock::CLASS_ID = -1; + +Textblock::Textblock (bool limitTextWidth) +{ + registerName ("dw::Textblock", &CLASS_ID); + setFlags (USES_HINTS); + + listItem = false; + innerPadding = 0; + line1Offset = 0; + line1OffsetEff = 0; + ignoreLine1OffsetSometimes = false; + mustQueueResize = false; + redrawY = 0; + lastWordDrawn = 0; + + /* + * The initial sizes of lines and words should not be + * too high, since this will waste much memory with tables + * containing many small cells. The few more calls to realloc + * should not decrease the speed considerably. + * (Current setting is for minimal memory usage. An interesting fact + * is that high values decrease speed due to memory handling overhead!) + * todo: Some tests would be useful. + */ + lines = new misc::SimpleVector <Line> (1); + words = new misc::SimpleVector <Word> (1); + + //DBG_OBJ_SET_NUM(page, "num_lines", num_lines); + + lastLineWidth = 0; + lastLineParMin = 0; + lastLineParMax = 0; + wrapRef = -1; + + //DBG_OBJ_SET_NUM(page, "last_line_width", last_line_width); + //DBG_OBJ_SET_NUM(page, "last_line_par_min", last_line_par_min); + //DBG_OBJ_SET_NUM(page, "last_line_par_max", last_line_par_max); + //DBG_OBJ_SET_NUM(page, "wrap_ref", wrap_ref); + + hoverLink = -1; + + // random values + availWidth = 100; + availAscent = 100; + availDescent = 0; + + hoverTooltip = NULL; + + this->limitTextWidth = limitTextWidth; + + for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) { + /* hlStart[layer].index > hlEnd[layer].index means no highlighting */ + hlStart[layer].index = 1; + hlStart[layer].nChar = 0; + hlEnd[layer].index = 0; + hlEnd[layer].nChar = 0; + } +} + +Textblock::~Textblock () +{ + //_MSG ("Textblock::~Textblock\n"); + + for (int i = 0; i < words->size(); i++) { + Word *word = words->getRef (i); + if (word->content.type == core::Content::WIDGET) + delete word->content.widget; + else if (word->content.type == core::Content::ANCHOR) + /* This also frees the names (see removeAnchor() and related). */ + removeAnchor(word->content.anchor); + + word->style->unref (); + word->spaceStyle->unref (); + } + + delete lines; + delete words; + + /* Make sure we don't own widgets anymore. Necessary before call of + parent class destructor. (???) */ + words = NULL; + + //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); +} + +/** + * The ascent of a textblock is the ascent of the first line, plus + * padding/border/margin. This can be used to align the first lines + * of several textblocks in a horizontal line. + */ +void Textblock::sizeRequestImpl (core::Requisition *requisition) +{ + rewrap (); + + if (lines->size () > 0) { + Line *lastLine = lines->getRef (lines->size () - 1); + requisition->width = + misc::max (lastLine->maxLineWidth, lastLineWidth); + /* Note: the break_space of the last line is ignored, so breaks + at the end of a textblock are not visible. */ + requisition->ascent = lines->getRef(0)->ascent; + requisition->descent = lastLine->top + + lastLine->ascent + lastLine->descent - lines->getRef(0)->ascent; + } else { + requisition->width = lastLineWidth; + requisition->ascent = 0; + requisition->descent = 0; + } + + requisition->width += innerPadding + getStyle()->boxDiffWidth (); + requisition->ascent += getStyle()->boxOffsetY (); + requisition->descent += getStyle()->boxRestHeight (); + + if (requisition->width < availWidth) + requisition->width = availWidth; +} + +/** + * Get the extremes of a word within a textblock. + */ +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; + } +} + +void Textblock::getExtremesImpl (core::Extremes *extremes) +{ + core::Extremes wordExtremes; + Line *line; + Word *word; + int wordIndex, lineIndex; + int parMin, parMax; + bool nowrap; + + //DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes"); + //DBG_MSG_START (widget); + + if (lines->size () == 0) { + /* empty page */ + extremes->minWidth = 0; + extremes->maxWidth = 0; + } else if (wrapRef == -1) { + /* no rewrap necessary -> values in lines are up to date */ + line = lines->getRef (lines->size () - 1); + /* Historical note: The former distinction between lines with and without + * words[first_word]->nowrap set is no longer necessary, since + * Dw_page_real_word_wrap sets max_word_min to the correct value in any + * case. */ + extremes->minWidth = line->maxWordMin; + extremes->maxWidth = misc::max (line->maxParMax, lastLineParMax); + //DBG_MSG (widget, "extremes", 0, "simple case"); + } else { + /* Calculate the extremes, based on the values in the line from + where a rewrap is necessary. */ + //DBG_MSG (widget, "extremes", 0, "complex case"); + + if (wrapRef == 0) { + extremes->minWidth = 0; + extremes->maxWidth = 0; + parMin = 0; + parMax = 0; + } else { + line = lines->getRef (wrapRef); + extremes->minWidth = line->maxWordMin; + extremes->maxWidth = line->maxParMax; + parMin = line->parMin; + parMax = line->parMax; + + //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); + } + + //_MSG ("*** parMin = %d\n", parMin); + + int prevWordSpace = 0; + for (lineIndex = wrapRef; lineIndex < lines->size (); lineIndex++) { + //DBG_MSGF (widget, "extremes", 0, "line %d", lineIndex); + //DBG_MSG_START (widget); + + line = lines->getRef (lineIndex); + nowrap = + words->getRef(line->firstWord)->style->whiteSpace + != core::style::WHITE_SPACE_NORMAL; + + //DEBUG_MSG (DEBUG_SIZE_LEVEL, " line %d (of %d), nowrap = %d\n", + // lineIndex, page->num_lines, nowrap); + + for (wordIndex = line->firstWord; wordIndex < line->lastWord; + wordIndex++) { + word = words->getRef (wordIndex); + getWordExtremes (word, &wordExtremes); + + /* For the first word, we simply add the line1_offset. */ + if (ignoreLine1OffsetSometimes && wordIndex == 0) { + wordExtremes.minWidth += line1Offset; + //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, + // " (next plus %d)\n", page->line1_offset); + } + + if (nowrap) { + parMin += prevWordSpace + wordExtremes.minWidth; + //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); + } else { + if (extremes->minWidth < wordExtremes.minWidth) + extremes->minWidth = wordExtremes.minWidth; + } + + //printf("parMax = %d, wordMaxWidth=%d, prevWordSpace=%d\n", + // parMax, wordExtremes.maxWidth, prevWordSpace); + if (word->content.type != core::Content::BREAK) + parMax += prevWordSpace; + parMax += wordExtremes.maxWidth; + prevWordSpace = word->origSpace; + + //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, + // " word %s: maxWidth = %d\n", + // a_Dw_content_text (&word->content), + // word_extremes.maxWidth); + } + + if ((line->lastWord > line->firstWord && + words->getRef(line->lastWord - 1)->content.type + == core::Content::BREAK ) || + lineIndex == lines->size () - 1 ) { + word = words->getRef (line->lastWord - 1); + + //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, + // " parMax = %d, after word %d (%s)\n", + // parMax, line->last_word - 1, + // a_Dw_content_text (&word->content)); + + if (extremes->maxWidth < parMax) + extremes->maxWidth = parMax; + + if (nowrap) { + //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); + if (extremes->minWidth < parMin) + extremes->minWidth = parMin; + + //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, + // " parMin = %d, after word %d (%s)\n", + // parMin, line->last_word - 1, + // a_Dw_content_text (&word->content)); + } + + prevWordSpace = 0; + parMin = 0; + parMax = 0; + } + + //DBG_MSG_END (widget); + } + + //DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, " Result: %d, %d\n", + // extremes->minWidth, extremes->maxWidth); + } + + //DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d", + // page->inner_padding, p_Dw_style_box_diff_width (widget->style)); + + int diff = innerPadding + getStyle()->boxDiffWidth (); + extremes->minWidth += diff; + extremes->maxWidth += diff; + + //DBG_MSG_END (widget); +} + + +void Textblock::sizeAllocateImpl (core::Allocation *allocation) +{ + int lineIndex, wordIndex; + Line *line; + Word *word; + int xCursor; + core::Allocation childAllocation; + core::Allocation *oldChildAllocation; + int wordInLine; + + if (allocation->width != this->allocation.width) { + redrawY = 0; + } + + for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) { + line = lines->getRef (lineIndex); + xCursor = lineXOffsetWidget (line); + + wordInLine = 0; + for (wordIndex = line->firstWord; wordIndex < line->lastWord; + wordIndex++) { + word = words->getRef (wordIndex); + + if (wordIndex == lastWordDrawn) { + redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + } + + switch (word->content.type) { + case core::Content::WIDGET: + /** \todo Justification within the line is done here. */ + childAllocation.x = xCursor + allocation->x; + /* align=top: + childAllocation.y = line->top + allocation->y; + */ + + /* 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->ascent - 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; + + oldChildAllocation = word->content.widget->getAllocation(); + + if (childAllocation.x != oldChildAllocation->x || + childAllocation.y != oldChildAllocation->y || + childAllocation.width != oldChildAllocation->width) { + /* The child widget has changed its position or its width + * so we need to redraw from this line onwards. + */ + redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + if (word->content.widget->wasAllocated ()) { + redrawY = misc::min (redrawY, + oldChildAllocation->y - this->allocation.y); + } + + } else if (childAllocation.ascent + childAllocation.descent != + oldChildAllocation->ascent + oldChildAllocation->descent) { + /* The child widget has changed its height. We need to redraw + * from where it changed. + * It's important not to draw from the line base, because the + * child might be a table covering the whole page so we would + * end up redrawing the whole screen over and over. + * The drawing of the child content is left to the child itself. + */ + int childChangedY = + misc::min(childAllocation.y - allocation->y + + childAllocation.ascent + childAllocation.descent, + oldChildAllocation->y - this->allocation.y + + oldChildAllocation->ascent + oldChildAllocation->descent); + + redrawY = misc::min (redrawY, childChangedY); + } + + word->content.widget->sizeAllocate (&childAllocation); + break; + + case core::Content::ANCHOR: + changeAnchor (word->content.anchor, + lineYOffsetCanvasAllocation (line, allocation)); + break; + + default: + wordInLine++; + // make compiler happy + break; + } + + xCursor += (word->size.width + word->effSpace); + } + } +} + +void Textblock::resizeDrawImpl () +{ + queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY); + if (lines->size () > 0) { + Line *lastLine = lines->getRef (lines->size () - 1); + /* Remember the last word that has been drawn so we can ensure to + * draw any new added words (see sizeAllocateImpl()). + */ + lastWordDrawn = lastLine->lastWord; + } + + redrawY = getHeight (); +} + +void Textblock::markSizeChange (int ref) +{ + markChange (ref); +} + +void Textblock::markExtremesChange (int ref) +{ + markChange (ref); +} + +/* + * Implementation for both mark_size_change and mark_extremes_change. + */ +void Textblock::markChange (int ref) +{ + if (ref != -1) { + //DBG_MSGF (page, "wrap", 0, "Dw_page_mark_size_change (ref = %d)", ref); + + if (wrapRef == -1) + wrapRef = ref; + else + wrapRef = misc::min (wrapRef, ref); + + //DBG_OBJ_SET_NUM (page, "wrap_ref", page->wrap_ref); + } +} + +void Textblock::setWidth (int width) +{ + /* If limitTextWidth is set to YES, a queue_resize may also be + * necessary. */ + if (availWidth != width || limitTextWidth) { + //DEBUG_MSG(DEBUG_REWRAP_LEVEL, + // "Dw_page_set_width: Calling p_Dw_widget_queue_resize, " + // "in page with %d word(s)\n", + // page->num_words); + + availWidth = width; + queueResize (0, false); + mustQueueResize = false; + redrawY = 0; + } +} + +void Textblock::setAscent (int ascent) +{ + if (availAscent != ascent) { + //DEBUG_MSG(DEBUG_REWRAP_LEVEL, + // "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, " + // "in page with %d word(s)\n", + // page->num_words); + + availAscent = ascent; + queueResize (0, false); + mustQueueResize = false; + } +} + +void Textblock::setDescent (int descent) +{ + if (availDescent != descent) { + //DEBUG_MSG(DEBUG_REWRAP_LEVEL, + // "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, " + // "in page with %d word(s)\n", + // page->num_words); + + availDescent = descent; + queueResize (0, false); + mustQueueResize = false; + } +} + +bool Textblock::buttonPressImpl (core::EventButton *event) +{ + return sendSelectionEvent (core::SelectionState::BUTTON_PRESS, event); +} + +bool Textblock::buttonReleaseImpl (core::EventButton *event) +{ + return sendSelectionEvent (core::SelectionState::BUTTON_RELEASE, event); +} + +bool Textblock::motionNotifyImpl (core::EventMotion *event) +{ + if (event->state & core::BUTTON1_MASK) + return sendSelectionEvent (core::SelectionState::BUTTON_MOTION, event); + else { + int linkOld, wordIndex; + core::style::Tooltip *tooltipOld; + + wordIndex = findWord (event->xWidget, event->yWidget); + + // cursor from word or widget style + if (wordIndex == -1) + setCursor (getStyle()->cursor); + else + setCursor (words->getRef(wordIndex)->style->cursor); + + linkOld = hoverLink; + tooltipOld = hoverTooltip; + + if (wordIndex == -1) { + hoverLink = -1; + hoverTooltip = NULL; + } else { + hoverLink = words->getRef(wordIndex)->style->x_link; + hoverTooltip = words->getRef(wordIndex)->style->x_tooltip; + } + + // Show/hide tooltip + if (tooltipOld != hoverTooltip) { + if (tooltipOld) + tooltipOld->onLeave (); + if (hoverTooltip) + hoverTooltip->onEnter (); + } else if (hoverTooltip) + hoverTooltip->onMotion (); + + if (hoverLink != linkOld) + return emitLinkEnter (hoverLink, -1, -1, -1); + else + return hoverLink != -1; + } +} + +void Textblock::enterNotifyImpl (core::EventCrossing *event) +{ +} + +void Textblock::leaveNotifyImpl (core::EventCrossing *event) +{ + hoverLink = -1; + (void) emitLinkEnter (hoverLink, -1, -1, -1); +} + +/** + * \brief Send event to selection. + */ +bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, + core::MousePositionEvent *event) +{ + core::Iterator *it; + Line *line, *lastLine; + int nextWordStartX, wordStartX, wordX, nextWordX, yFirst, yLast; + int charPos = 0, prevPos, wordIndex, lineIndex, link; + Word *word; + bool found, withinContent, r; + + if (words->size () == 0) + // no contens at all + return false; + + // In most cases true, so set here: + link = -1; + withinContent = true; + + lastLine = lines->getRef (lines->size () - 1); + yFirst = lineYOffsetCanvasI (0); + yLast = + lineYOffsetCanvas (lastLine) + lastLine->ascent + lastLine->descent; + if (event->yCanvas < yFirst) { + // Above the first line: take the first word. + withinContent = false; + wordIndex = 0; + charPos = 0; + } else if (event->yCanvas >= yLast) { + // Below the last line: take the last word. + withinContent = false; + wordIndex = words->size () - 1; + word = words->getRef (wordIndex); + charPos = word->content.type == core::Content::TEXT ? + strlen (word->content.text) : 0; + } else { + lineIndex = findLineIndex (event->yWidget); + line = lines->getRef (lineIndex); + + // Pointer within the break space? + if (event->yWidget > + (lineYOffsetWidget (line) + line->ascent + line->descent)) { + // Choose this break. + withinContent = false; + wordIndex = line->lastWord - 1; + charPos = 0; + } else if (event->xWidget < lineXOffsetWidget (line)) { + // Left of the first word in the line. + wordIndex = line->firstWord; + withinContent = false; + charPos = 0; + } else { + nextWordStartX = lineXOffsetWidget (line); + found = false; + for (wordIndex = line->firstWord; + !found && wordIndex < line->lastWord; + wordIndex++) { + word = words->getRef (wordIndex); + wordStartX = nextWordStartX; + nextWordStartX += word->size.width + word->effSpace; + + if (event->xWidget >= wordStartX && + event->xWidget < nextWordStartX) { + // We have found the word. + if (word->content.type == core::Content::TEXT) { + // Search the character the mouse pointer is in. + // nextWordX is the right side of this character. + charPos = 0; + while ((nextWordX = wordStartX + + layout->textWidth (word->style->font, + word->content.text, charPos)) + <= event->xWidget) + charPos = layout->nextGlyph (word->content.text, charPos); + + // The left side of this character. + prevPos = layout->prevGlyph (word->content.text, charPos); + wordX = wordStartX + layout->textWidth (word->style->font, + word->content.text, + prevPos); + + // If the mouse pointer is left from the middle, use the left + // position, otherwise, use the right one. + if (event->xWidget <= (wordX + nextWordX) / 2) + charPos = prevPos; + } else { + // Depends on whether the pointer is within the left or + // right half of the (non-text) word. + if (event->xWidget >= + (wordStartX + nextWordStartX) / 2) + charPos = core::SelectionState::END_OF_WORD; + else + charPos = 0; + } + + found = true; + link = word->style ? word->style->x_link : -1; + break; + } + } + + if (!found) { + // No word found in this line (i.e. we are on the right side), + // take the last of this line. + withinContent = false; + wordIndex = line->lastWord - 1; + if (wordIndex >= words->size ()) + wordIndex--; + word = words->getRef (wordIndex); + charPos = word->content.type == core::Content::TEXT ? + strlen (word->content.text) : + (int)core::SelectionState::END_OF_WORD; + } + } + } + + it = new TextblockIterator (this, core::Content::SELECTION_CONTENT, + wordIndex); + r = selectionHandleEvent (eventType, it, charPos, link, event, + withinContent); + it->unref (); + return r; +} + +void Textblock::removeChild (Widget *child) +{ + /** \bug Not implemented. */ +} + +core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) +{ + return new TextblockIterator (this, mask, atEnd); +} + +/* + * ... + * + * availWidth is passed from wordWrap, to avoid calculating it twice. + */ +void Textblock::justifyLine (Line *line, int availWidth) +{ + /* To avoid rounding errors, the calculation is based on accumulated + * values (*_cum). */ + int i; + int origSpaceSum, origSpaceCum; + int effSpaceDiffCum, lastEffSpaceDiffCum; + int diff; + + diff = availWidth - lastLineWidth; + if (diff > 0) { + origSpaceSum = 0; + for (i = line->firstWord; i < line->lastWord - 1; i++) + origSpaceSum += words->getRef(i)->origSpace; + + origSpaceCum = 0; + lastEffSpaceDiffCum = 0; + for (i = line->firstWord; i < line->lastWord - 1; i++) { + origSpaceCum += words->getRef(i)->origSpace; + + if (origSpaceCum == 0) + effSpaceDiffCum = lastEffSpaceDiffCum; + else + effSpaceDiffCum = diff * origSpaceCum / origSpaceSum; + + words->getRef(i)->effSpace = words->getRef(i)->origSpace + + (effSpaceDiffCum - lastEffSpaceDiffCum); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", i, + // page->words[i].eff_space); + + lastEffSpaceDiffCum = effSpaceDiffCum; + } + } +} + + +void Textblock::addLine (int wordInd, bool newPar) +{ + Line *lastLine, *plastLine; + + //DBG_MSG (page, "wrap", 0, "Dw_page_add_line"); + //DBG_MSG_START (page); + + lines->increase (); + //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); + + //DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d" + // "\n", page->num_lines - 1, page, word_ind, page->num_words); + + lastLine = lines->getRef (lines->size () - 1); + + if (lines->size () == 1) + plastLine = NULL; + else + plastLine = lines->getRef (lines->size () - 2); + + if (plastLine) { + /* second or more lines: copy values of last line */ + lastLine->top = + plastLine->top + plastLine->ascent + + plastLine->descent + plastLine->breakSpace; + lastLine->maxLineWidth = plastLine->maxLineWidth; + lastLine->maxWordMin = plastLine->maxWordMin; + lastLine->maxParMax = plastLine->maxParMax; + lastLine->parMin = plastLine->parMin; + lastLine->parMax = plastLine->parMax; + } else { + /* first line: initialize values */ + lastLine->top = 0; + lastLine->maxLineWidth = line1OffsetEff; + lastLine->maxWordMin = 0; + lastLine->maxParMax = 0; + lastLine->parMin = line1OffsetEff; + lastLine->parMax = line1OffsetEff; + } + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.top", page->num_lines - 1, + // lastLine->top); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxLineWidth", page->num_lines - 1, + // lastLine->maxLineWidth); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxWordMin", page->num_lines - 1, + // lastLine->maxWordMin); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, + // lastLine->maxParMax); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, + // lastLine->parMin); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, + // lastLine->parMax); + + lastLine->firstWord = wordInd; + lastLine->ascent = 0; + lastLine->descent = 0; + lastLine->marginDescent = 0; + lastLine->breakSpace = 0; + lastLine->leftOffset = 0; + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, + // lastLine->ascent); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, + // lastLine->descent); + + /* update values in line */ + lastLine->maxLineWidth = misc::max (lastLine->maxLineWidth, lastLineWidth); + + if (lines->size () > 1) + lastLineWidth = 0; + else + lastLineWidth = line1OffsetEff; + + if (newPar) { + lastLine->maxParMax = misc::max (lastLine->maxParMax, lastLineParMax); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, + // lastLine->maxParMax); + + if (lines->size () > 1) { + lastLine->parMin = 0; + lastLine->parMax = 0; + } else { + lastLine->parMin = line1OffsetEff; + lastLine->parMax = line1OffsetEff; + } + lastLineParMin = 0; + lastLineParMax = 0; + + //DBG_OBJ_SET_NUM(page, "lastLineParMin", page->lastLineParMin); + //DBG_OBJ_SET_NUM(page, "lastLineParMax", page->lastLineParMax); + } + + lastLine->parMin = lastLineParMin; + lastLine->parMax = lastLineParMax; + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, + // lastLine->parMin); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, + // lastLine->parMax); + + //DBG_MSG_END (page); +} + +/* + * This method is called in two cases: (i) when a word is added (by + * Dw_page_add_word), and (ii) when a page has to be (partially) + * rewrapped. It does word wrap, and adds new lines, if necesary. + */ +void Textblock::wordWrap(int wordIndex) +{ + Line *lastLine; + Word *word, *prevWord; + int availWidth, lastSpace, leftOffset; + bool newLine = false, newPar = false; + core::Extremes wordExtremes; + + //DBG_MSGF (page, "wrap", 0, "Dw_page_real_word_wrap (%d): %s, width = %d", + // word_ind, a_Dw_content_html (&page->words[word_ind].content), + // page->words[word_ind].size.width); + //DBG_MSG_START (page); + + availWidth = this->availWidth - getStyle()->boxDiffWidth() - innerPadding; + if (limitTextWidth && + layout->getUsesViewport () && + availWidth > layout->getWidthViewport () - 10) + availWidth = layout->getWidthViewport () - 10; + + word = words->getRef (wordIndex); + + if (lines->size () == 0) { + //DBG_MSG (page, "wrap", 0, "first line"); + newLine = true; + newPar = true; + lastLine = NULL; + } else { + lastLine = lines->getRef (lines->size () - 1); + + if (lines->size () > 0) { + prevWord = words->getRef (wordIndex - 1); + if (prevWord->content.type == core::Content::BREAK) { + //DBG_MSG (page, "wrap", 0, "after a break"); + /* previous word is a break */ + newLine = true; + newPar = true; + } else if (word->style->whiteSpace + != core::style::WHITE_SPACE_NORMAL) { + //DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)", + // word->style->white_space); + newLine = false; + newPar = false; + } else { + if (lastLine->firstWord != wordIndex) { + /* Does new word fit into the last line? */ + //DBG_MSGF (page, "wrap", 0, + // "word %d (%s) fits? (%d + %d + %d <= %d)...", + // word_ind, a_Dw_content_html (&word->content), + // page->lastLine_width, prevWord->orig_space, + // word->size.width, availWidth); + newLine = (lastLineWidth + prevWord->origSpace + + word->size.width > availWidth); + //DBG_MSGF (page, "wrap", 0, "... %s.", + // newLine ? "No" : "Yes"); + } + } + } + } + + /* Has sometimes the wrong value. */ + word->effSpace = word->origSpace; + //DBG_OBJ_ARRSET_NUM (page,"words.%d.eff_space", word_ind, word->eff_space); + + /* Test, whether line1_offset can be used. */ + if (wordIndex == 0) { + if (ignoreLine1OffsetSometimes) { + if (line1Offset + word->size.width > availWidth) + line1OffsetEff = 0; + else + line1OffsetEff = line1Offset; + } else + line1OffsetEff = line1Offset; + } + + if (lastLine != NULL && newLine && !newPar && + word->style->textAlign == core::style::TEXT_ALIGN_JUSTIFY) + justifyLine (lastLine, availWidth); + + if (newLine) { + addLine (wordIndex, newPar); + lastLine = lines->getRef (lines->size () - 1); + } + + lastLine->lastWord = wordIndex + 1; + lastLine->ascent = misc::max (lastLine->ascent, (int) word->size.ascent); + lastLine->descent = misc::max (lastLine->descent, (int) word->size.descent); + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, + // lastLine->ascent); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, + // lastLine->descent); + + if (word->content.type == core::Content::WIDGET) { + lastLine->marginDescent = + misc::max (lastLine->marginDescent, + word->size.descent + + word->content.widget->getStyle()->margin.bottom); + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, + // lastLine->descent); + + /* If the widget is not in the first line of the paragraph, its top + * margin may make the line higher. + */ + if (lines->size () > 1) { + /* Here, we know already what the break and the bottom margin + * contributed to the space before this line. + */ + lastLine->ascent = + misc::max (lastLine->ascent, + word->size.ascent + + word->content.widget->getStyle()->margin.top); + + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, + // lastLine->ascent); + } + } else + lastLine->marginDescent = + misc::max (lastLine->marginDescent, lastLine->descent); + + getWordExtremes (word, &wordExtremes); + lastSpace = (wordIndex > 0) ? words->getRef(wordIndex - 1)->origSpace : 0; + + if (word->content.type == core::Content::BREAK) + lastLine->breakSpace = + misc::max (word->content.breakSpace, + lastLine->marginDescent - lastLine->descent, + lastLine->breakSpace); + + lastLineWidth += word->size.width; + if (!newLine) + lastLineWidth += lastSpace; + + lastLineParMin += wordExtremes.maxWidth; + lastLineParMax += wordExtremes.maxWidth; + if (!newPar) { + lastLineParMin += lastSpace; + lastLineParMax += lastSpace; + } + + if (word->style->whiteSpace != core::style::WHITE_SPACE_NORMAL) { + lastLine->parMin += wordExtremes.minWidth + lastSpace; + /* This may also increase the accumulated minimum word width. */ + lastLine->maxWordMin = + misc::max (lastLine->maxWordMin, lastLine->parMin); + /* NOTE: Most code relies on that all values of nowrap are equal for all + * words within one line. */ + } else + /* Simple case. */ + lastLine->maxWordMin = + misc::max (lastLine->maxWordMin, wordExtremes.minWidth); + + //DBG_OBJ_SET_NUM(page, "lastLine_par_min", page->lastLine_par_min); + //DBG_OBJ_SET_NUM(page, "lastLine_par_max", page->lastLine_par_max); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_min", page->num_lines - 1, + // lastLine->par_min); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_max", page->num_lines - 1, + // lastLine->par_max); + //DBG_OBJ_ARRSET_NUM (page, "lines.%d.max_word_min", page->num_lines - 1, + // lastLine->max_word_min); + + /* Finally, justify the line. Breaks are ignored, since the HTML + * parser sometimes assignes the wrong style to them. (todo: ) */ + if (word->content.type != core::Content::BREAK) { + switch (word->style->textAlign) { + case core::style::TEXT_ALIGN_LEFT: + case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ + case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the + * future) */ + leftOffset = 0; + break; + + case core::style::TEXT_ALIGN_RIGHT: + leftOffset = availWidth - lastLineWidth; + break; + + case core::style::TEXT_ALIGN_CENTER: + leftOffset = (availWidth - lastLineWidth) / 2; + break; + + default: + /* compiler happiness */ + leftOffset = 0; + } + + /* For large lines (images etc), which do not fit into the viewport: */ + if (leftOffset < 0) + leftOffset = 0; + + if (listItem && lastLine == lines->getRef (0)) { + /* List item markers are always on the left. */ + lastLine->leftOffset = 0; + words->getRef(0)->effSpace = words->getRef(0)->origSpace + leftOffset; + //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", 0, + // page->words[0].eff_space); + } else + lastLine->leftOffset = leftOffset; + } + + mustQueueResize = true; + + //DBG_MSG_END (page); +} + + +/** + * 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; + + /* 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); + size->ascent -= widget->getStyle()->margin.top; + size->descent -= widget->getStyle()->margin.bottom; + } else { + /* TODO: Use margin.{top|bottom} here, like above. + * (No harm for the next future.) */ + if (widget->getStyle()->width == core::style::LENGTH_AUTO || + widget->getStyle()->height == core::style::LENGTH_AUTO) + widget->sizeRequest (&requisition); + + if (widget->getStyle()->width == core::style::LENGTH_AUTO) + size->width = requisition.width; + else if (core::style::isAbsLength (widget->getStyle()->width)) + /* Fixed lengths are only applied to the content, so we have to + * add padding, border and margin. */ + size->width = core::style::absLengthVal (widget->getStyle()->width) + + widget->getStyle()->boxDiffWidth (); + else + size->width = + (int) (core::style::perLengthVal (widget->getStyle()->width) + * availWidth); + + if (widget->getStyle()->height == core::style::LENGTH_AUTO) { + size->ascent = requisition.ascent; + size->descent = requisition.descent; + } else if (core::style::isAbsLength (widget->getStyle()->height)) { + /* Fixed lengths are only applied to the content, so we have to + * add padding, border and margin. */ + size->ascent = + core::style::absLengthVal (widget->getStyle()->height) + + widget->getStyle()->boxDiffHeight (); + size->descent = 0; + } else { + double len = core::style::perLengthVal (widget->getStyle()->height); + size->ascent = (int) (len * availAscent); + size->descent = (int) (len * availDescent); + } + } +} + +/** + * Rewrap the page from the line from which this is necessary. + * There are basically two times we'll want to do this: + * either when the viewport is resized, or when the size changes on one + * of the child widgets. + */ +void Textblock::rewrap () +{ + int i, wordIndex; + Word *word; + Line *lastLine; + + if (wrapRef == -1) + /* page does not have to be rewrapped */ + return; + + //DBG_MSGF (page, "wrap", 0, + // "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)", + // page->wrap_ref, page->num_words); + //DBG_MSG_START (page); + + /* All lines up from page->wrap_ref will be rebuild from the word list, + * the line list up from this position is rebuild. */ + lines->setSize (wrapRef); + lastLineWidth = 0; + //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); + //DBG_OBJ_SET_NUM(page, "lastLine_width", page->lastLine_width); + + /* In the word list, we start at the last word, plus one (see definition + * of last_word), in the line before. */ + if (wrapRef > 0) { + /* Note: In this case, Dw_page_real_word_wrap will immediately find + * the need to rewrap the line, since we start with the last one (plus + * one). This is also the reason, why page->lastLine_width is set + * to the length of the line. */ + lastLine = lines->getRef (lines->size () - 1); + + lastLineParMin = lastLine->parMin; + lastLineParMax = lastLine->parMax; + + wordIndex = lastLine->lastWord; + for (i = lastLine->firstWord; i < lastLine->lastWord - 1; i++) + lastLineWidth += (words->getRef(i)->size.width + + words->getRef(i)->origSpace); + lastLineWidth += words->getRef(lastLine->lastWord - 1)->size.width; + } else { + lastLineParMin = 0; + lastLineParMax = 0; + + wordIndex = 0; + } + + for (; wordIndex < words->size (); wordIndex++) { + word = words->getRef (wordIndex); + + if (word->content.type == core::Content::WIDGET) + calcWidgetSize (word->content.widget, &word->size); + wordWrap (wordIndex); + + if (word->content.type == core::Content::WIDGET) { + word->content.widget->parentRef = lines->size () - 1; + //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", + // word->content.widget->parent_ref); + } + + //DEBUG_MSG(DEBUG_REWRAP_LEVEL, + // "Assigning parent_ref = %d to rewrapped word %d, " + // "in page with %d word(s)\n", + // page->num_lines - 1, wordIndex, page->num_words); + + /* todo_refactoring: + if (word->content.type == DW_CONTENT_ANCHOR) + p_Dw_gtk_viewport_change_anchor + (widget, word->content.anchor, + Dw_page_line_total_y_offset (page, + &page->lines[page->num_lines - 1])); + */ + } + + /* Next time, the page will not have to be rewrapped. */ + wrapRef = -1; + + //DBG_MSG_END (page); +} + +/* + * Paint a line + * - x and y are toplevel dw coordinates (Question: what Dw? Changed. Test!) + * - area is used always (ev. set it to event->area) + * - event is only used when is_expose + */ +void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) +{ + Word *word; + int wordIndex; + int xWidget, yWidget, xWorld, yWorld, yWorldBase; + int startHL, widthHL; + int wordLen; + int diff, effHLStart, effHLEnd, layer; + core::Widget *child; + core::Rectangle childArea; + core::style::Color *color, *thisBgColor, *wordBgColor; + + /* Here's an idea on how to optimize this routine to minimize the number + * of calls to gdk_draw_string: + * + * Copy the text from the words into a buffer, adding a new word + * only if: the attributes match, and the spacing is either zero or + * equal to the width of ' '. In the latter case, copy a " " into + * the buffer. Then draw the buffer. */ + + thisBgColor = getBgColor (); + + xWidget = lineXOffsetWidget(line); + xWorld = allocation.x + xWidget; + yWidget = lineYOffsetWidget (line); + yWorld = allocation.y + yWidget; + yWorldBase = yWorld + line->ascent; + + for (wordIndex = line->firstWord; wordIndex < line->lastWord; + wordIndex++) { + word = words->getRef(wordIndex); + diff = 0; + color = word->style->color; + + //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.x", wordIndex, + // xWidget); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.<i>drawn at</i>.y", wordIndex, + // yWidget); + + switch (word->content.type) { + case core::Content::TEXT: + if (word->style->backgroundColor) + wordBgColor = word->style->backgroundColor; + else + wordBgColor = thisBgColor; + + /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */ + if (word->style->valign == core::style::VALIGN_SUB) + diff = word->size.ascent / 2; + else if (word->style->valign == core::style::VALIGN_SUPER) + diff -= word->size.ascent / 3; + + /* Draw background (color, image), when given. */ + if (word->style->hasBackground () && word->size.width > 0) + drawBox (view, word->style, area, + xWidget, yWidget + line->ascent - word->size.ascent, + word->size.width, word->size.ascent + word->size.descent, + false); + + /* Draw space background (color, image), when given. */ + if (word->spaceStyle->hasBackground () && word->effSpace > 0) + drawBox (view, word->spaceStyle, area, + xWidget + word->size.width, + yWidget + line->ascent - word->size.ascent, + word->effSpace, word->size.ascent + word->size.descent, + false); + view->drawText (word->style->font, color, + core::style::Color::SHADING_NORMAL, + xWorld, yWorldBase + diff, + word->content.text, strlen (word->content.text)); + + /* underline */ + if (word->style->textDecoration & + core::style::TEXT_DECORATION_UNDERLINE) + view->drawLine (color, core::style::Color::SHADING_NORMAL, + xWorld, yWorldBase + 1 + diff, + xWorld + word->size.width - 1, + yWorldBase + 1 + diff); + if (wordIndex + 1 < line->lastWord && + (word->spaceStyle->textDecoration + & core::style::TEXT_DECORATION_UNDERLINE)) + view->drawLine (word->spaceStyle->color, + core::style::Color::SHADING_NORMAL, + xWorld + word->size.width, + yWorldBase + 1 + diff, + xWorld + word->size.width + word->effSpace - 1, + yWorldBase + 1 + diff); + + /* strike-through */ + if (word->style->textDecoration + & core::style::TEXT_DECORATION_LINE_THROUGH) + view->drawLine (color, core::style::Color::SHADING_NORMAL, + xWorld, + yWorldBase - word->size.ascent / 2 + diff, + xWorld + word->size.width - 1, + yWorldBase - word->size.ascent / 2 + diff); + if (wordIndex + 1 < line->lastWord && + (word->spaceStyle->textDecoration + & core::style::TEXT_DECORATION_LINE_THROUGH)) + view->drawLine (word->spaceStyle->color, + core::style::Color::SHADING_NORMAL, + xWorld + word->size.width, + yWorldBase - word->size.ascent / 2 + diff, + xWorld + word->size.width + word->effSpace - 1, + yWorldBase - word->size.ascent / 2 + diff); + + for (layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) { + if (hlStart[layer].index <= wordIndex && + hlEnd[layer].index >= wordIndex) { + + wordLen = strlen (word->content.text); + effHLEnd = misc::min (wordLen, hlEnd[layer].nChar); + effHLStart = 0; + if (wordIndex == hlStart[layer].index) + effHLStart = misc::min (hlStart[layer].nChar, wordLen); + + effHLEnd = wordLen; + if (wordIndex == hlEnd[layer].index) + effHLEnd = misc::min (hlEnd[layer].nChar, wordLen); + + startHL = xWorld + layout->textWidth (word->style->font, + word->content.text, + effHLStart); + widthHL = + layout->textWidth (word->style->font, + word->content.text + effHLStart, + effHLEnd - effHLStart); + + // If the space after this word highlighted, and this word + // is not the last one in this line, highlight also the + // space. + /** \todo This should also be done with spaces after non-text + * words, but this is not yet defined very well. */ + if (wordIndex < hlEnd[layer].index && + wordIndex < words->size () && + wordIndex != line->lastWord - 1) + widthHL += word->effSpace; + + + if (widthHL != 0) { + /* Draw background for highlighted text. */ + view->drawRectangle (wordBgColor, + core::style::Color::SHADING_INVERSE, + true, startHL, + yWorldBase - word->size.ascent, + widthHL, + word->size.ascent + word->size.descent); + + /* Highlight the text. */ + view->drawText (word->style->font, + color, core::style::Color::SHADING_INVERSE, + startHL, yWorldBase + diff, + word->content.text + effHLStart, + effHLEnd - effHLStart); + + /* underline and strike-through */ + if (word->style->textDecoration + & core::style::TEXT_DECORATION_UNDERLINE) + view->drawLine (color, + core::style::Color::SHADING_INVERSE, + startHL, yWorldBase + 1 + diff, + startHL + widthHL - 1, + yWorldBase + 1 + diff); + if (word->style->textDecoration + & core::style::TEXT_DECORATION_LINE_THROUGH) + view->drawLine (color, + core::style::Color::SHADING_INVERSE, + startHL, + yWorldBase - word->size.ascent / 2 + diff, + startHL + widthHL - 1, + yWorldBase - word->size.ascent / 2 + + diff); + } + } + } + break; + + case core::Content::WIDGET: + child = word->content.widget; + if (child->intersects (area, &childArea)) + child->draw (view, &childArea); + break; + + case core::Content::ANCHOR: case core::Content::BREAK: + /* nothing - an anchor/break isn't seen */ + /* + * Historical note: + * > BUG: sometimes anchors have x_space; + * > we subtract that just in case --EG + * This is inconsistent with other parts of the code, so it should + * be tried to prevent this earlier.--SG + */ + /* + * x_viewport -= word->size.width + word->eff_space; + * xWidget -= word->size.width + word->eff_space; + */ +#if 0 + /* Useful for testing: draw breaks. */ + if (word->content.type == DW_CONTENT_BREAK) + gdk_draw_rectangle (window, color, TRUE, + p_Dw_widget_xWorld_to_viewport (widget, + widget->allocation.x + + Dw_page_line_total_x_offset(page, line)), + y_viewport_base + line->descent, + DW_WIDGET_CONTENT_WIDTH(widget), + word->content.break_space); +#endif + break; + + default: + fprintf (stderr, "BUG!!! at (%d, %d).\n", xWorld, yWorldBase + diff); + break; + } + + xWorld += word->size.width + word->effSpace; + xWidget += word->size.width + word->effSpace; + } +} + +/** + * Find the first line index that includes y, relative to top of widget. + */ +int Textblock::findLineIndex (int y) +{ + int maxIndex = lines->size () - 1; + int step, index, low = 0; + + step = (lines->size() + 1) >> 1; + while ( step > 1 ) { + index = low + step; + if (index <= maxIndex && + lineYOffsetWidgetI (index) < y) + low = index; + step = (step + 1) >> 1; + } + + if (low < maxIndex && lineYOffsetWidgetI (low + 1) < y) + low++; + + /* + * This new routine returns the line number between (top) and + * (top + size.ascent + size.descent + break_space): the space + * _below_ the line is considered part of the line. Old routine + * returned line number between (top - previous_line->break_space) + * and (top + size.ascent + size.descent): the space _above_ the + * line was considered part of the line. This is important for + * Dw_page_find_link() --EG + * That function has now been inlined into Dw_page_motion_notify() --JV + */ + return low; +} + +/** + * \brief Find the line of word \em wordIndex. + */ +int Textblock::findLineOfWord (int wordIndex) +{ + int high = lines->size () - 1, index, low = 0; + + //g_return_val_if_fail (word_index >= 0, -1); + //g_return_val_if_fail (word_index < page->num_words, -1); + + while (true) { + index = (low + high) / 2; + if (wordIndex >= lines->getRef(index)->firstWord) { + if (wordIndex < lines->getRef(index)->lastWord) + return index; + else + low = index + 1; + } else + high = index - 1; + } +} + +/** + * \brief Find the index of the word, or -1. + */ +int Textblock::findWord (int x, int y) +{ + int lineIndex, wordIndex; + int xCursor, lastXCursor; + Line *line; + Word *word; + + if ((lineIndex = findLineIndex (y)) >= lines->size ()) + return -1; + line = lines->getRef (lineIndex); + if (lineYOffsetWidget (line) + line->ascent + line->descent <= y) + return -1; + + xCursor = lineXOffsetWidget (line); + for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) { + word = words->getRef (wordIndex); + lastXCursor = xCursor; + xCursor += word->size.width + word->effSpace; + if (lastXCursor <= x && xCursor > x) + return wordIndex; + } + + return -1; +} + +void Textblock::draw (core::View *view, core::Rectangle *area) +{ + int lineIndex; + Line *line; + + drawWidgetBox (view, area, false); + + lineIndex = findLineIndex (area->y); + + for (; lineIndex < lines->size (); lineIndex++) { + line = lines->getRef (lineIndex); + if (lineYOffsetWidget (line) >= area->y + area->height) + break; + + drawLine (line, view, area); + } +} + +/** + * Add a new word (text, widget etc.) to a page. + */ +Textblock::Word *Textblock::addWord (int width, int ascent, int descent, + core::style::Style *style) +{ + Word *word; + + words->increase (); + + word = words->getRef (words->size() - 1); + word->size.width = width; + word->size.ascent = ascent; + word->size.descent = descent; + word->origSpace = 0; + word->effSpace = 0; + word->content.space = false; + + //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.width", page->num_words - 1, + // word->size.width); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.descent", page->num_words - 1, + // word->size.descent); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.ascent", page->num_words - 1, + // word->size.ascent); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", page->num_words - 1, + // word->orig_space); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", page->num_words - 1, + // word->eff_space); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", page->num_words - 1, + // word->content.space); + + word->style = style; + word->spaceStyle = style; + style->ref (); + style->ref (); + + return word; +} + +/** + * Calculate the size of a text word. + */ +void Textblock::calcTextSize (const char *text, core::style::Style *style, + core::Requisition *size) +{ + size->width = + layout->textWidth (style->font, text, strlen (text)); + size->ascent = style->font->ascent; + size->descent = style->font->descent; + + /* In case of a sub or super script we increase the word's height and + * potentially the line's height. + */ + if (style->valign == core::style::VALIGN_SUB) + size->descent += (size->ascent / 2); + else if (style->valign == core::style::VALIGN_SUPER) + size->ascent += (size->ascent / 3); +} + + +/** + * Add a word to the page structure. Stashes the argument pointer in + * the page data structure so that it will be deallocated on destroy. + */ +void Textblock::addText (const char *text, core::style::Style *style) +{ + Word *word; + core::Requisition size; + + calcTextSize (text, style, &size); + word = addWord (size.width, size.ascent, size.descent, style); + word->content.type = core::Content::TEXT; + word->content.text = layout->textZone->strdup(text); + + //DBG_OBJ_ARRSET_STR (page, "words.%d.content.text", page->num_words - 1, + // word->content.text); + + wordWrap (words->size () - 1); +} + +/** + * Add a widget (word type) to the page. + */ +void Textblock::addWidget (core::Widget *widget, core::style::Style *style) +{ + Word *word; + core::Requisition size; + + /* We first assign -1 as parent_ref, since the call of widget->size_request + * will otherwise let this DwPage 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; + + widget->setParent (this); + widget->setStyle (style); + + calcWidgetSize (widget, &size); + word = addWord (size.width, size.ascent, size.descent, style); + + word->content.type = core::Content::WIDGET; + word->content.widget = widget; + + //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", page->num_words - 1, + // word->content.widget); + + wordWrap (words->size () - 1); + word->content.widget->parentRef = lines->size () - 1; + //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", + // word->content.widget->parent_ref); + + //DEBUG_MSG(DEBUG_REWRAP_LEVEL, + // "Assigning parent_ref = %d to added word %d, " + // "in page with %d word(s)\n", + // page->num_lines - 1, page->num_words - 1, page->num_words); +} + + +/** + * Add an anchor to the page. "name" is copied, so no strdup is neccessary for + * the caller. + * + * Return true on success, and false, when this anchor had already been + * added to the widget tree. + */ +bool Textblock::addAnchor (const char *name, core::style::Style *style) +{ + Word *word; + char *copy; + int y; + + // Since an anchor does not take any space, it is safe to call + // addAnchor already here. + if (wasAllocated ()) { + if (lines->size () == 0) + y = allocation.y; + else + y = allocation.y + lineYOffsetWidgetI (lines->size () - 1); + copy = Widget::addAnchor (name, y); + } else + copy = Widget::addAnchor (name); + + if (copy == NULL) + /** + * \todo It may be neccessary for future uses to save the anchor in + * some way, e.g. when parts of the widget tree change. + */ + return false; + else { + word = addWord (0, 0, 0, style); + word->content.type = core::Content::ANCHOR; + word->content.anchor = copy; + wordWrap (words->size () - 1); + return true; + } +} + + +/** + * ? + */ +void Textblock::addSpace (core::style::Style *style) +{ + int nl, nw; + int space; + + nl = lines->size () - 1; + if (nl >= 0) { + nw = words->size () - 1; + if (nw >= 0) { + /* todo: remove this test case */ + //if (page->words[nw].orig_space != 0) { + // _MSG(" a_Dw_page_add_space:: already existing space!!!\n"); + //} + + space = style->font->spaceWidth; + words->getRef(nw)->origSpace = space; + words->getRef(nw)->effSpace = space; + words->getRef(nw)->content.space = true; + + //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", nw, + // page->words[nw].orig_space); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", nw, + // page->words[nw].eff_space); + //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", nw, + // page->words[nw].content.space); + + words->getRef(nw)->spaceStyle->unref (); + words->getRef(nw)->spaceStyle = style; + style->ref (); + } + } +} + + +/** + * Cause a paragraph break + */ +void Textblock::addParbreak (int space, core::style::Style *style) +{ + Word *word, *word2 = NULL; // Latter for compiler happiness, search! + bool isfirst; + Widget *widget; + int lineno; + + /* 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.) */ + if (words->size () == 0 || + (listItem && words->size () == 1)) { + /* This is a bit hackish: If a break is added as the + first/second word of a page, and the parent widget is also a + DwPage, and there is a break before -- this is the case when + 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. */ + + /* Find the widget where to adjust the break_space. */ + for (widget = this; + widget->getParent() && + widget->getParent()->instanceOf (Textblock::CLASS_ID); + widget = widget->getParent ()) { + Textblock *textblock2 = (Textblock*)widget->getParent (); + if (textblock2->listItem) + isfirst = (textblock2->words->get(1).content.type + == core::Content::WIDGET + && textblock2->words->get(1).content.widget == widget); + else + isfirst = (textblock2->words->get(0).content.type + == core::Content::WIDGET + && textblock2->words->get(0).content.widget == widget); + if (!isfirst) { + /* The page we searched for has been found. */ + lineno = widget->parentRef; + if (lineno > 0 && + (word2 = + textblock2->words->getRef(textblock2->lines + ->get(lineno - 1).firstWord)) && + word2->content.type == core::Content::BREAK) { + if (word2->content.breakSpace < space) { + word2->content.breakSpace = space; + textblock2->queueResize (lineno, false); + textblock2->mustQueueResize = false; + } + } + return; + } + /* Otherwise continue to examine parents. */ + } + /* Return in any case. */ + return; + } + + /* Another break before? */ + if ((word = words->getRef(words->size () - 1)) && + word->content.type == core::Content::BREAK) { + Line *lastLine = lines->getRef (lines->size () - 1); + + word->content.breakSpace = + misc::max (word->content.breakSpace, space); + lastLine->breakSpace = + misc::max (word->content.breakSpace, + lastLine->marginDescent - lastLine->descent, + lastLine->breakSpace); + return; + } + + word = addWord (0, 0, 0, style); + word->content.type = core::Content::BREAK; + word->content.breakSpace = space; + wordWrap (words->size () - 1); +} + +/* + * Cause a line break. + */ +void Textblock::addLinebreak (core::style::Style *style) +{ + Word *word; + + if (words->size () == 0 || + words->get(words->size () - 1).content.type == core::Content::BREAK) + // An <BR> in an empty line gets the height of the current font + // (why would someone else place it here?), ... + word = addWord (0, style->font->ascent, style->font->descent, style); + else + // ... otherwise, it has no size (and does not enlarge the line). + word = addWord (0, 0, 0, style); + + word->content.type = core::Content::BREAK; + word->content.breakSpace = 0; + word->style = style; + wordWrap (words->size () - 1); +} + + +/** + * \brief Search recursively through widget. + * + * This is an optimized version of the general + * dw::core::Widget::getWidgetAtPoint method. + */ +core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) +{ + int lineIndex, wordIndex; + Line *line; + + if (x < allocation.x || + y < allocation.y || + x > allocation.x + allocation.width || + y > allocation.y + getHeight ()) { + return NULL; + } + + lineIndex = findLineIndex (y - allocation.y); + + if (lineIndex < 0 || lineIndex >= lines->size ()) { + return this; + } + + line = lines->getRef (lineIndex); + + for (wordIndex = line->firstWord; wordIndex < line->lastWord; wordIndex++) { + Word *word = words->getRef (wordIndex); + + if (word->content.type == core::Content::WIDGET) { + core::Widget * childAtPoint; + childAtPoint = word->content.widget->getWidgetAtPoint (x, y, + level + 1); + if (childAtPoint) { + return childAtPoint; + } + } + } + + return this; +} + + +/** + * This function "hands" the last break of a page "over" to a parent + * page. This is used for "collapsing spaces". + */ +void Textblock::handOverBreak (core::style::Style *style) +{ + #if 0 + MISSING + DwPageLine *last_line; + DwWidget *parent; + + if (page->num_lines == 0) + return; + + last_line = &page->lines[page->num_lines - 1]; + if (last_line->break_space != 0 && + (parent = DW_WIDGET(page)->parent) && DW_IS_PAGE (parent)) + a_Dw_page_add_parbreak (DW_PAGE (parent), last_line->break_space, style); +#endif +} + +/* + * Any words added by a_Dw_page_add_... are not immediately (queued to + * be) drawn, instead, this function must be called. This saves some + * calls to p_Dw_widget_queue_resize. + * + */ +void Textblock::flush () +{ + if (mustQueueResize) { + queueResize (-1, true); + mustQueueResize = false; + } +} + + +// next: Dw_page_find_word + +void Textblock::changeLinkColor (int link, int newColor) +{ +} + +void Textblock::changeWordStyle (int from, int to, core::style::Style *style, + bool includeFirstSpace, bool includeLastSpace) +{ +} + +// ---------------------------------------------------------------------- + +Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, + core::Content::Type mask, + bool atEnd): + core::Iterator (textblock, mask, atEnd) +{ + index = atEnd ? textblock->words->size () : -1; + content.type = atEnd ? core::Content::END : core::Content::START; +} + +Textblock::TextblockIterator::TextblockIterator (Textblock *textblock, + core::Content::Type mask, + int index): + core::Iterator (textblock, mask, false) +{ + this->index = index; + + if (index < 0) + content.type = core::Content::START; + else if (index >= textblock->words->size ()) + content.type = core::Content::END; + else + content = textblock->words->get(index).content; +} + +object::Object *Textblock::TextblockIterator::clone() +{ + return new TextblockIterator ((Textblock*)getWidget(), getMask(), index); +} + +int Textblock::TextblockIterator::compareTo(misc::Comparable *other) +{ + return index - ((TextblockIterator*)other)->index; +} + +bool Textblock::TextblockIterator::next () +{ + Textblock *textblock = (Textblock*)getWidget(); + + if (content.type == core::Content::END) + return false; + + do { + index++; + if (index >= textblock->words->size ()) { + content.type = core::Content::END; + return false; + } + } while ((textblock->words->get(index).content.type & getMask()) == 0); + + content = textblock->words->get(index).content; + return true; +} + +bool Textblock::TextblockIterator::prev () +{ + Textblock *textblock = (Textblock*)getWidget(); + + if (content.type == core::Content::START) + return false; + + do { + index--; + if (index < 0) { + content.type = core::Content::START; + return false; + } + } while ((textblock->words->get(index).content.type & getMask()) == 0); + + content = textblock->words->get(index).content; + return true; +} + +void Textblock::TextblockIterator::highlight (int start, int end, + core::HighlightLayer layer) +{ + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) { + /* nothing is highlighted */ + textblock->hlStart[layer].index = index; + textblock->hlEnd[layer].index = index; + } + + if (textblock->hlStart[layer].index >= index) { + index2 = textblock->hlStart[layer].index; + textblock->hlStart[layer].index = index; + textblock->hlStart[layer].nChar = start; + } + + if (textblock->hlEnd[layer].index <= index) { + index2 = textblock->hlEnd[layer].index; + textblock->hlEnd[layer].index = index; + textblock->hlEnd[layer].nChar = end; + } + + textblock->queueDrawRange (index1, index2); +} + +void Textblock::TextblockIterator::unhighlight (int direction, + core::HighlightLayer layer) +{ + Textblock *textblock = (Textblock*)getWidget(); + int index1 = index, index2 = index; + + if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) + return; + + if (direction == 0) { + index1 = textblock->hlStart[layer].index; + index2 = textblock->hlEnd[layer].index; + textblock->hlStart[layer].index = 1; + textblock->hlEnd[layer].index = 0; + } else if (direction > 0 && textblock->hlStart[layer].index <= index) { + index1 = textblock->hlStart[layer].index; + textblock->hlStart[layer].index = index + 1; + textblock->hlStart[layer].nChar = 0; + } else if (direction < 0 && textblock->hlEnd[layer].index >= index) { + index1 = textblock->hlEnd[layer].index; + textblock->hlEnd[layer].index = index - 1; + textblock->hlEnd[layer].nChar = INT_MAX; + } + + textblock->queueDrawRange (index1, index2); +} + +void Textblock::queueDrawRange (int index1, int index2) +{ + int from = misc::min (index1, index2); + int to = misc::max (index1, index2); + + from = misc::min (from, words->size () - 1); + from = misc::max (from, 0); + to = misc::min (to, words->size () - 1); + to = misc::max (to, 0); + + int line1 = findLineOfWord (from); + int line2 = findLineOfWord (to); + + queueDrawArea (0, + lineYOffsetWidgetI (line1), + allocation.width, + lineYOffsetWidgetI (line2) + - lineYOffsetWidgetI (line1) + + lines->getRef (line2)->ascent + + lines->getRef (line2)->descent); +} + +void Textblock::TextblockIterator::getAllocation (int start, int end, + core::Allocation *allocation) +{ + Textblock *textblock = (Textblock*)getWidget(); + int lineIndex = textblock->findLineOfWord (index); + Line *line = textblock->lines->getRef (lineIndex); + Word *word = textblock->words->getRef (index); + + allocation->x = + textblock->allocation.x + textblock->lineXOffsetWidget (line); + for (int i = line->firstWord; i < index; i++) + allocation->x += textblock->words->getRef(i)->size.width; + + allocation->y = + textblock->allocation.y + + textblock->lineYOffsetWidget (line) + line->ascent - word->size.ascent; + allocation->width = word->size.width; + allocation->ascent = word->size.ascent; + allocation->descent = word->size.descent; +} + +} // namespace dw |