1 /**
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2003, 2004, 2005, 2006, 2010 Apple Inc. All rights reserved.
5 * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 *
22 */
23
24 #include "config.h"
25 #include "RenderListItem.h"
26
27 #include "CachedImage.h"
28 #include "HTMLNames.h"
29 #include "HTMLOListElement.h"
30 #include "RenderListMarker.h"
31 #include "RenderView.h"
32 #include <wtf/StdLibExtras.h>
33
34 using namespace std;
35
36 namespace WebCore {
37
38 using namespace HTMLNames;
39
RenderListItem(Node * node)40 RenderListItem::RenderListItem(Node* node)
41 : RenderBlock(node)
42 , m_marker(0)
43 , m_hasExplicitValue(false)
44 , m_isValueUpToDate(false)
45 , m_notInList(false)
46 {
47 setInline(false);
48 }
49
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)50 void RenderListItem::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
51 {
52 RenderBlock::styleDidChange(diff, oldStyle);
53
54 if (style()->listStyleType() != NoneListStyle
55 || (style()->listStyleImage() && !style()->listStyleImage()->errorOccurred())) {
56 RefPtr<RenderStyle> newStyle = RenderStyle::create();
57 // The marker always inherits from the list item, regardless of where it might end
58 // up (e.g., in some deeply nested line box). See CSS3 spec.
59 newStyle->inheritFrom(style());
60 if (!m_marker)
61 m_marker = new (renderArena()) RenderListMarker(this);
62 m_marker->setStyle(newStyle.release());
63 } else if (m_marker) {
64 m_marker->destroy();
65 m_marker = 0;
66 }
67 }
68
destroy()69 void RenderListItem::destroy()
70 {
71 if (m_marker) {
72 m_marker->destroy();
73 m_marker = 0;
74 }
75 RenderBlock::destroy();
76 }
77
isList(Node * node)78 static bool isList(Node* node)
79 {
80 return (node->hasTagName(ulTag) || node->hasTagName(olTag));
81 }
82
enclosingList(Node * node)83 static Node* enclosingList(Node* node)
84 {
85 Node* parent = node->parentNode();
86 for (Node* n = parent; n; n = n->parentNode())
87 if (isList(n))
88 return n;
89 // If there's no actual <ul> or <ol> list element, then our parent acts as
90 // our list for purposes of determining what other list items should be
91 // numbered as part of the same list.
92 return parent;
93 }
94
enclosingList(const RenderObject * renderer)95 static Node* enclosingList(const RenderObject* renderer)
96 {
97 Node* node = renderer->node();
98 if (node)
99 return enclosingList(node);
100
101 renderer = renderer->parent();
102 while (renderer && !renderer->node())
103 renderer = renderer->parent();
104
105 node = renderer->node();
106 if (isList(node))
107 return node;
108
109 return enclosingList(node);
110 }
111
previousListItem(Node * list,const RenderListItem * item)112 static RenderListItem* previousListItem(Node* list, const RenderListItem* item)
113 {
114 for (RenderObject* renderer = item->previousInPreOrder(); renderer != list->renderer(); renderer = renderer->previousInPreOrder()) {
115 if (!renderer->isListItem())
116 continue;
117 Node* otherList = enclosingList(renderer);
118 // This item is part of our current list, so it's what we're looking for.
119 if (list == otherList)
120 return toRenderListItem(renderer);
121 // We found ourself inside another list; lets skip the rest of it.
122 // Use nextInPreOrder() here because the other list itself may actually
123 // be a list item itself. We need to examine it, so we do this to counteract
124 // the previousInPreOrder() that will be done by the loop.
125 if (otherList)
126 renderer = otherList->renderer()->nextInPreOrder();
127 }
128 return 0;
129 }
130
calcValue() const131 inline int RenderListItem::calcValue() const
132 {
133 if (m_hasExplicitValue)
134 return m_explicitValue;
135 Node* list = enclosingList(this);
136 // FIXME: This recurses to a possible depth of the length of the list.
137 // That's not good -- we need to change this to an iterative algorithm.
138 if (RenderListItem* previousItem = previousListItem(list, this))
139 return previousItem->value() + 1;
140 if (list && list->hasTagName(olTag))
141 return static_cast<HTMLOListElement*>(list)->start();
142 return 1;
143 }
144
updateValueNow() const145 void RenderListItem::updateValueNow() const
146 {
147 m_value = calcValue();
148 m_isValueUpToDate = true;
149 }
150
isEmpty() const151 bool RenderListItem::isEmpty() const
152 {
153 return lastChild() == m_marker;
154 }
155
getParentOfFirstLineBox(RenderBlock * curr,RenderObject * marker)156 static RenderObject* getParentOfFirstLineBox(RenderBlock* curr, RenderObject* marker)
157 {
158 RenderObject* firstChild = curr->firstChild();
159 if (!firstChild)
160 return 0;
161
162 for (RenderObject* currChild = firstChild; currChild; currChild = currChild->nextSibling()) {
163 if (currChild == marker)
164 continue;
165
166 if (currChild->isInline() && (!currChild->isRenderInline() || curr->generatesLineBoxesForInlineChild(currChild)))
167 return curr;
168
169 if (currChild->isFloating() || currChild->isPositioned())
170 continue;
171
172 if (currChild->isTable() || !currChild->isRenderBlock())
173 break;
174
175 if (curr->isListItem() && currChild->style()->htmlHacks() && currChild->node() &&
176 (currChild->node()->hasTagName(ulTag)|| currChild->node()->hasTagName(olTag)))
177 break;
178
179 RenderObject* lineBox = getParentOfFirstLineBox(toRenderBlock(currChild), marker);
180 if (lineBox)
181 return lineBox;
182 }
183
184 return 0;
185 }
186
updateValue()187 void RenderListItem::updateValue()
188 {
189 if (!m_hasExplicitValue) {
190 m_isValueUpToDate = false;
191 if (m_marker)
192 m_marker->setNeedsLayoutAndPrefWidthsRecalc();
193 }
194 }
195
firstNonMarkerChild(RenderObject * parent)196 static RenderObject* firstNonMarkerChild(RenderObject* parent)
197 {
198 RenderObject* result = parent->firstChild();
199 while (result && result->isListMarker())
200 result = result->nextSibling();
201 return result;
202 }
203
updateMarkerLocation()204 void RenderListItem::updateMarkerLocation()
205 {
206 // Sanity check the location of our marker.
207 if (m_marker) {
208 RenderObject* markerPar = m_marker->parent();
209 RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker);
210 if (!lineBoxParent) {
211 // If the marker is currently contained inside an anonymous box,
212 // then we are the only item in that anonymous box (since no line box
213 // parent was found). It's ok to just leave the marker where it is
214 // in this case.
215 if (markerPar && markerPar->isAnonymousBlock())
216 lineBoxParent = markerPar;
217 else
218 lineBoxParent = this;
219 }
220
221 if (markerPar != lineBoxParent || m_marker->prefWidthsDirty()) {
222 // Removing and adding the marker can trigger repainting in
223 // containers other than ourselves, so we need to disable LayoutState.
224 view()->disableLayoutState();
225 updateFirstLetter();
226 m_marker->remove();
227 if (!lineBoxParent)
228 lineBoxParent = this;
229 lineBoxParent->addChild(m_marker, firstNonMarkerChild(lineBoxParent));
230 if (m_marker->prefWidthsDirty())
231 m_marker->calcPrefWidths();
232 view()->enableLayoutState();
233 }
234 }
235 }
236
calcPrefWidths()237 void RenderListItem::calcPrefWidths()
238 {
239 ASSERT(prefWidthsDirty());
240
241 updateMarkerLocation();
242
243 RenderBlock::calcPrefWidths();
244 }
245
layout()246 void RenderListItem::layout()
247 {
248 ASSERT(needsLayout());
249
250 updateMarkerLocation();
251 RenderBlock::layout();
252 }
253
positionListMarker()254 void RenderListItem::positionListMarker()
255 {
256 if (m_marker && m_marker->parent()->isBox() && !m_marker->isInside() && m_marker->inlineBoxWrapper()) {
257 int markerOldX = m_marker->x();
258 int yOffset = 0;
259 int xOffset = 0;
260 for (RenderBox* o = m_marker->parentBox(); o != this; o = o->parentBox()) {
261 yOffset += o->y();
262 xOffset += o->x();
263 }
264
265 bool adjustOverflow = false;
266 int markerXPos;
267 RootInlineBox* root = m_marker->inlineBoxWrapper()->root();
268
269 // FIXME: Inline flows in the line box hierarchy that have self-painting layers should act as cutoff points
270 // and really shouldn't keep propagating overflow up. This won't really break anything other than repainting
271 // not being as tight as it could be though.
272 if (style()->direction() == LTR) {
273 int leftLineOffset = leftRelOffset(yOffset, leftOffset(yOffset, false), false);
274 markerXPos = leftLineOffset - xOffset - paddingLeft() - borderLeft() + m_marker->marginLeft();
275 m_marker->inlineBoxWrapper()->adjustPosition(markerXPos - markerOldX, 0);
276 for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
277 if (markerXPos < box->leftLayoutOverflow()) {
278 box->setHorizontalOverflowPositions(markerXPos, box->rightLayoutOverflow(), box->leftVisualOverflow(), box->rightVisualOverflow());
279 if (box == root)
280 adjustOverflow = true;
281 }
282 }
283 } else {
284 int rightLineOffset = rightRelOffset(yOffset, rightOffset(yOffset, false), false);
285 markerXPos = rightLineOffset - xOffset + paddingRight() + borderRight() + m_marker->marginLeft();
286 m_marker->inlineBoxWrapper()->adjustPosition(markerXPos - markerOldX, 0);
287 for (InlineFlowBox* box = m_marker->inlineBoxWrapper()->parent(); box; box = box->parent()) {
288 if (markerXPos + m_marker->width() > box->rightLayoutOverflow()) {
289 box->setHorizontalOverflowPositions(box->leftLayoutOverflow(), markerXPos + m_marker->width(), box->leftVisualOverflow(), box->rightVisualOverflow());
290 if (box == root)
291 adjustOverflow = true;
292 }
293 }
294 }
295
296 if (adjustOverflow) {
297 IntRect markerRect(markerXPos + xOffset, yOffset, m_marker->width(), m_marker->height());
298 RenderBox* o = m_marker;
299 do {
300 o = o->parentBox();
301 if (o->isRenderBlock())
302 toRenderBlock(o)->addLayoutOverflow(markerRect);
303 markerRect.move(-o->x(), -o->y());
304 } while (o != this && !o->hasSelfPaintingLayer());
305 }
306 }
307 }
308
paint(PaintInfo & paintInfo,int tx,int ty)309 void RenderListItem::paint(PaintInfo& paintInfo, int tx, int ty)
310 {
311 if (!height())
312 return;
313
314 RenderBlock::paint(paintInfo, tx, ty);
315 }
316
markerText() const317 const String& RenderListItem::markerText() const
318 {
319 if (m_marker)
320 return m_marker->text();
321 DEFINE_STATIC_LOCAL(String, staticNullString, ());
322 return staticNullString;
323 }
324
explicitValueChanged()325 void RenderListItem::explicitValueChanged()
326 {
327 if (m_marker)
328 m_marker->setNeedsLayoutAndPrefWidthsRecalc();
329 Node* listNode = enclosingList(node());
330 RenderObject* listRenderer = 0;
331 if (listNode)
332 listRenderer = listNode->renderer();
333 for (RenderObject* renderer = this; renderer; renderer = renderer->nextInPreOrder(listRenderer))
334 if (renderer->isListItem()) {
335 RenderListItem* item = toRenderListItem(renderer);
336 if (!item->m_hasExplicitValue) {
337 item->m_isValueUpToDate = false;
338 if (RenderListMarker* marker = item->m_marker)
339 marker->setNeedsLayoutAndPrefWidthsRecalc();
340 }
341 }
342 }
343
setExplicitValue(int value)344 void RenderListItem::setExplicitValue(int value)
345 {
346 ASSERT(node());
347
348 if (m_hasExplicitValue && m_explicitValue == value)
349 return;
350 m_explicitValue = value;
351 m_value = value;
352 m_hasExplicitValue = true;
353 explicitValueChanged();
354 }
355
clearExplicitValue()356 void RenderListItem::clearExplicitValue()
357 {
358 ASSERT(node());
359
360 if (!m_hasExplicitValue)
361 return;
362 m_hasExplicitValue = false;
363 m_isValueUpToDate = false;
364 explicitValueChanged();
365 }
366
367 } // namespace WebCore
368