initial import
[vuplus_webkit] / Source / WebCore / editing / ModifySelectionListLevel.cpp
1 /*
2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "ModifySelectionListLevel.h"
28
29 #include "Document.h"
30 #include "Frame.h"
31 #include "FrameSelection.h"
32 #include "HTMLElement.h"
33 #include "RenderObject.h"
34 #include "htmlediting.h"
35
36 namespace WebCore {
37
38 ModifySelectionListLevelCommand::ModifySelectionListLevelCommand(Document* document) 
39     : CompositeEditCommand(document)
40 {
41 }
42
43 bool ModifySelectionListLevelCommand::preservesTypingStyle() const
44 {
45     return true;
46 }
47
48 // This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel
49 static bool getStartEndListChildren(const VisibleSelection& selection, Node*& start, Node*& end)
50 {
51     if (selection.isNone())
52         return false;
53
54     // start must be in a list child
55     Node* startListChild = enclosingListChild(selection.start().anchorNode());
56     if (!startListChild)
57         return false;
58
59     // end must be in a list child
60     Node* endListChild = selection.isRange() ? enclosingListChild(selection.end().anchorNode()) : startListChild;
61     if (!endListChild)
62         return false;
63     
64     // For a range selection we want the following behavior:
65     //      - the start and end must be within the same overall list
66     //      - the start must be at or above the level of the rest of the range
67     //      - if the end is anywhere in a sublist lower than start, the whole sublist gets moved
68     // In terms of this function, this means:
69     //      - endListChild must start out being be a sibling of startListChild, or be in a
70     //         sublist of startListChild or a sibling
71     //      - if endListChild is in a sublist of startListChild or a sibling, it must be adjusted
72     //         to be the ancestor that is startListChild or its sibling
73     while (startListChild->parentNode() != endListChild->parentNode()) {
74         endListChild = endListChild->parentNode();
75         if (!endListChild)
76             return false;
77     }
78     
79     // if the selection ends on a list item with a sublist, include the entire sublist
80     if (endListChild->renderer()->isListItem()) {
81         RenderObject* r = endListChild->renderer()->nextSibling();
82         if (r && isListElement(r->node()))
83             endListChild = r->node();
84     }
85
86     start = startListChild;
87     end = endListChild;
88     return true;
89 }
90
91 void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(Node* startNode, Node* endNode, Node* refNode)
92 {
93     Node* node = startNode;
94     while (1) {
95         Node* next = node->nextSibling();
96         removeNode(node);
97         insertNodeBefore(node, refNode);
98
99         if (node == endNode)
100             break;
101
102         node = next;
103     }
104 }
105
106 void ModifySelectionListLevelCommand::insertSiblingNodeRangeAfter(Node* startNode, Node* endNode, Node* refNode)
107 {
108     Node* node = startNode;
109     while (1) {
110         Node* next = node->nextSibling();
111         removeNode(node);
112         insertNodeAfter(node, refNode);
113
114         if (node == endNode)
115             break;
116
117         refNode = node;
118         node = next;
119     }
120 }
121
122 void ModifySelectionListLevelCommand::appendSiblingNodeRange(Node* startNode, Node* endNode, Element* newParent)
123 {
124     Node* node = startNode;
125     while (1) {
126         Node* next = node->nextSibling();
127         removeNode(node);
128         appendNode(node, newParent);
129
130         if (node == endNode)
131             break;
132
133         node = next;
134     }
135 }
136
137 IncreaseSelectionListLevelCommand::IncreaseSelectionListLevelCommand(Document* document, Type listType)
138     : ModifySelectionListLevelCommand(document)
139     , m_listType(listType)
140 {
141 }
142
143 // This needs to be static so it can be called by canIncreaseSelectionListLevel
144 static bool canIncreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end)
145 {
146     if (!getStartEndListChildren(selection, start, end))
147         return false;
148         
149     // start must not be the first child (because you need a prior one
150     // to increase relative to)
151     if (!start->renderer()->previousSibling())
152         return false;
153     
154     return true;
155 }
156
157 // For the moment, this is SPI and the only client (Mail.app) is satisfied.
158 // Here are two things to re-evaluate when making into API.
159 // 1. Currently, InheritedListType uses clones whereas OrderedList and
160 // UnorderedList create a new list node of the specified type.  That is
161 // inconsistent wrt style.  If that is not OK, here are some alternatives:
162 //  - new nodes always inherit style (probably the best choice)
163 //  - new nodes have always have no style
164 //  - new nodes of the same type inherit style
165 // 2. Currently, the node we return may be either a pre-existing one or
166 // a new one. Is it confusing to return the pre-existing one without
167 // somehow indicating that it is not new?  If so, here are some alternatives:
168 //  - only return the list node if we created it
169 //  - indicate whether the list node is new or pre-existing
170 //  - (silly) client specifies whether to return pre-existing list nodes
171 void IncreaseSelectionListLevelCommand::doApply()
172 {
173     Node* startListChild;
174     Node* endListChild;
175     if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild))
176         return;
177
178     Node* previousItem = startListChild->renderer()->previousSibling()->node();
179     if (isListElement(previousItem)) {
180         // move nodes up into preceding list
181         appendSiblingNodeRange(startListChild, endListChild, static_cast<Element*>(previousItem));
182         m_listElement = previousItem;
183     } else {
184         // create a sublist for the preceding element and move nodes there
185         RefPtr<Element> newParent;
186         switch (m_listType) {
187             case InheritedListType:
188                 newParent = startListChild->parentElement();
189                 if (newParent)
190                     newParent = newParent->cloneElementWithoutChildren();
191                 break;
192             case OrderedList:
193                 newParent = createOrderedListElement(document());
194                 break;
195             case UnorderedList:
196                 newParent = createUnorderedListElement(document());
197                 break;
198         }
199         insertNodeBefore(newParent, startListChild);
200         appendSiblingNodeRange(startListChild, endListChild, newParent.get());
201         m_listElement = newParent.release();
202     }
203 }
204
205 bool IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(Document* document)
206 {
207     Node* startListChild;
208     Node* endListChild;
209     return canIncreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
210 }
211
212 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document, Type type)
213 {
214     ASSERT(document);
215     ASSERT(document->frame());
216     RefPtr<IncreaseSelectionListLevelCommand> command = create(document, type);
217     command->apply();
218     return command->m_listElement.release();
219 }
220
221 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevel(Document* document)
222 {
223     return increaseSelectionListLevel(document, InheritedListType);
224 }
225
226 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(Document* document)
227 {
228     return increaseSelectionListLevel(document, OrderedList);
229 }
230
231 PassRefPtr<Node> IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(Document* document)
232 {
233     return increaseSelectionListLevel(document, UnorderedList);
234 }
235
236 DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document* document) 
237     : ModifySelectionListLevelCommand(document)
238 {
239 }
240
241 // This needs to be static so it can be called by canDecreaseSelectionListLevel
242 static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end)
243 {
244     if (!getStartEndListChildren(selection, start, end))
245         return false;
246     
247     // there must be a destination list to move the items to
248     if (!isListElement(start->parentNode()->parentNode()))
249         return false;
250         
251     return true;
252 }
253
254 void DecreaseSelectionListLevelCommand::doApply()
255 {
256     Node* startListChild;
257     Node* endListChild;
258     if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild))
259         return;
260
261     Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->node() : 0;
262     Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 0;
263     Element* listNode = startListChild->parentElement();
264
265     if (!previousItem) {
266         // at start of sublist, move the child(ren) to before the sublist
267         insertSiblingNodeRangeBefore(startListChild, endListChild, listNode);
268         // if that was the whole sublist we moved, remove the sublist node
269         if (!nextItem)
270             removeNode(listNode);
271     } else if (!nextItem) {
272         // at end of list, move the child(ren) to after the sublist
273         insertSiblingNodeRangeAfter(startListChild, endListChild, listNode);    
274     } else if (listNode) {
275         // in the middle of list, split the list and move the children to the divide
276         splitElement(listNode, startListChild);
277         insertSiblingNodeRangeBefore(startListChild, endListChild, listNode);
278     }
279 }
280
281 bool DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(Document* document)
282 {
283     Node* startListChild;
284     Node* endListChild;
285     return canDecreaseListLevel(document->frame()->selection()->selection(), startListChild, endListChild);
286 }
287
288 void DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(Document* document)
289 {
290     ASSERT(document);
291     ASSERT(document->frame());
292     applyCommand(create(document));
293 }
294
295 }