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